package com.zbkj.admin.oss.oss.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.StrUtil;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.*;
import com.amazonaws.services.s3.model.metrics.MetricsConfiguration;
import com.amazonaws.util.IOUtils;
import com.zbkj.admin.boot.redisson.command.Redis;
import com.zbkj.admin.oss.core.FileProperties;
import com.zbkj.admin.oss.core.FileTemplate;
import lombok.Cleanup;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URL;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * aws-s3 通用存储操作 支持所有兼容s3协议的云存储: {阿里云OSS，腾讯云COS，七牛云，京东云，minio 等}
 *
 * @author lengleng
 * @author 858695266
 * @date 2020/5/23 6:36 上午
 * @since 1.0
 */
@Component
@RequiredArgsConstructor
public class OssTemplate implements InitializingBean, FileTemplate {

    private final FileProperties properties;

    private AmazonS3 amazonS3;
    private final Redis redis;

    /**
     * 创建bucket
     *
     * @param bucketName bucket名称
     */
    public void createBucket(String bucketName) {
        if (!amazonS3.doesBucketExistV2(bucketName)) {
            amazonS3.createBucket((bucketName));
        }
    }

    /**
     * 获取全部bucket
     * <p>
     *
     * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/ListBuckets">AWS
     * API Documentation</a>
     */
    public List<Bucket> getAllBuckets() {
        return amazonS3.listBuckets();
    }

    /**
     * 获取图片文件列表
     *
     * @param bucketName 存储桶名称
     * @param directory  目录路径（可为空）
     * @param imageTypes 允许的图片类型（如 jpg,png）
     * @param maxKeys    单次最大返回数量
     */
    public Map<String, Object> getImageListPage(String bucketName, String directory,
                                                Set<String> imageTypes,
                                                Integer maxKeys,
                                                String continuationToken) {
        ListObjectsV2Request request = new ListObjectsV2Request()
                .withBucketName(bucketName)
                .withPrefix(StrUtil.isBlank(directory) ? null : directory.replace('\\', '/'))
                .withDelimiter(null)
                .withMaxKeys(18); // 设置分页token
        if (StrUtil.isNotBlank(continuationToken) && !Validator.isNumber(continuationToken) && !StrUtil.isNullOrUndefined(continuationToken)) {
            request.setContinuationToken(continuationToken);
        }

        ListObjectsV2Result response = amazonS3.listObjectsV2(request);

        List<S3ObjectSummary> pageResult = response.getObjectSummaries().stream()
                .filter(s3Obj -> isImageType(s3Obj.getKey(), imageTypes))
                .collect(Collectors.toList());
        AtomicReference<List<S3ObjectSummary>> dataList = new AtomicReference<>();
        AtomicReference<String> token = new AtomicReference<>();
        AtomicReference<ListObjectsV2Result> res = new AtomicReference<>();
        while (CollUtil.isEmpty(dataList.get())) {
            res.set(amazonS3.listObjectsV2(bucketName, res.get() != null ? token.get() : response.getNextContinuationToken()));
            token.set(res.get().getNextContinuationToken());
            dataList.set(res.get().getObjectSummaries().stream()
                    .filter(s3Obj -> isImageType(s3Obj.getKey(), imageTypes))
                    .collect(Collectors.toList()));
        }

        Map<String, Object> result = new HashMap<>();
        result.put("list", pageResult.stream().peek(s3Obj -> s3Obj.setKey(properties.getOss().getEndpoint() + "/" + bucketName + "/" + s3Obj.getKey()))
                .map(S3ObjectSummary::getKey).map(s3Obj -> Dict.of().set("sattDir", s3Obj)).toList());
        result.put("nextToken", response.getNextContinuationToken()); // 下一页token
        result.put("isTruncated", response.isTruncated()); // 是否还有更多结果
        // 在返回结果中加入
        if (maxKeys == 1) {
            result.put("total", pageResult.size() + (response.isTruncated() ? getTotalCount(bucketName, response.getNextContinuationToken(), imageTypes) : 0));
            redis.setEx("fileNextToken" + 1, result.get("total"), Duration.ofSeconds(30L));
        } else {
            result.put("total", redis.get("fileNextToken" + 1));
        }
        return result;
    }

    // 新增获取总数方法
//    public int getObjectCount(String bucketName) {
//        GetBucketMetricsConfigurationResult entireBucket = amazonS3.getBucketMetricsConfiguration(bucketName, "EntireBucket");
//        return metricsConfig.getMetrics().getStorageBytes().intValue();
//    }


    // 新增递归统计方法
    private int getTotalCount(String bucketName, String continuationToken, Set<String> imageTypes) {
        ListObjectsV2Request request = new ListObjectsV2Request()
                .withBucketName(bucketName)
                .withContinuationToken(continuationToken);

        ListObjectsV2Result response = amazonS3.listObjectsV2(request);
        List<S3ObjectSummary> pageResult = response.getObjectSummaries().stream()
                .filter(s3Obj -> isImageType(s3Obj.getKey(), imageTypes))
                .collect(Collectors.toList());
        return pageResult.size() + (response.isTruncated() ? getTotalCount(bucketName, response.getNextContinuationToken(), imageTypes) : 0);
    }

    private boolean isImageType(String key, Set<String> imageTypes) {
        if (imageTypes == null || imageTypes.isEmpty()) return true;
        String fileExt = FileUtil.getSuffix(key);
        return imageTypes.contains(fileExt);
    }

    /**
     * @param bucketName bucket名称
     * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/ListBuckets">AWS
     * API Documentation</a>
     */
    public Optional<Bucket> getBucket(String bucketName) {
        return amazonS3.listBuckets().stream().filter(b -> b.getName().equals(bucketName)).findFirst();
    }

    /**
     * @param bucketName bucket名称
     * @see <a href=
     * "http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/DeleteBucket">AWS API
     * Documentation</a>
     */
    public void removeBucket(String bucketName) {
        amazonS3.deleteBucket(bucketName);
    }

    /**
     * 根据文件前置查询文件
     *
     * @param bucketName bucket名称
     * @param prefix     前缀
     * @param recursive  是否递归查询
     * @return S3ObjectSummary 列表
     * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/ListObjects">AWS
     * API Documentation</a>
     */
    public List<S3ObjectSummary> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive) {
        ObjectListing objectListing = amazonS3.listObjects(bucketName, prefix);
        return new ArrayList<>(objectListing.getObjectSummaries());
    }

    /**
     * 获取文件外链
     *
     * @param bucketName bucket名称
     * @param objectName 文件名称
     * @param expires    过期时间 <=7
     * @return url
     * @see AmazonS3#generatePresignedUrl(String bucketName, String key, Date expiration)
     */
    public String getObjectURL(String bucketName, String objectName, Integer expires) {
        Date date = new Date();
        Calendar calendar = new GregorianCalendar();
        calendar.setTime(date);
        calendar.add(Calendar.DAY_OF_MONTH, expires);
        URL url = amazonS3.generatePresignedUrl(bucketName, objectName, calendar.getTime());
        return url.toString();
    }

    /**
     * 获取文件
     *
     * @param bucketName bucket名称
     * @param objectName 文件名称
     * @return 二进制流
     * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/GetObject">AWS
     * API Documentation</a>
     */
    public S3Object getObject(String bucketName, String objectName) {
        if (properties.getOss().isSkipMd5Check()) {
            System.setProperty("com.amazonaws.services.s3.disableGetObjectMD5Validation", "false");
        }
        return amazonS3.getObject(bucketName, objectName);
    }

    /**
     * 获取文件
     *
     * @param bucketName bucket名称
     * @param dir        文件夹名称
     * @param objectName 文件名称
     * @return 二进制流 API Documentation</a>
     */
    @Override
    public S3Object getObject(String bucketName, String dir, String objectName) {
        if (StrUtil.isNotBlank(dir)) {
            objectName = dir + FileUtil.FILE_SEPARATOR + objectName;
        }
        return getObject(bucketName, objectName);
    }

    /**
     * 上传文件
     *
     * @param bucketName bucket名称
     * @param objectName 文件名称
     * @param stream     文件流
     * @throws Exception
     */
    public void putObject(String bucketName, String objectName, InputStream stream) throws Exception {
        putObject(bucketName, objectName, stream, stream.available(), "application/octet-stream");
    }

    /**
     * 上传文件
     *
     * @param bucketName  bucket名称
     * @param objectName  文件名称
     * @param stream      文件流
     * @param contextType 文件类型
     * @throws Exception
     */
    public void putObject(String bucketName, String objectName, InputStream stream, String contextType)
            throws Exception {
        putObject(bucketName, objectName, stream, stream.available(), contextType);
    }

    /**
     * 上传文件
     *
     * @param bucketName  bucket名称
     * @param dir         文件夹名称
     * @param objectName  文件名称
     * @param stream      文件流
     * @param contextType 文件类型
     * @throws Exception
     */
    @Override
    public void putObject(String bucketName, String dir, String objectName, InputStream stream, String contextType)
            throws Exception {
        if (StrUtil.isNotBlank(dir)) {
            // dir 路径为 a/b
            objectName = dir + FileUtil.FILE_SEPARATOR + objectName;
        }

        putObject(bucketName, objectName, stream, stream.available(), contextType);
    }

    /**
     * 上传文件
     *
     * @param bucketName  bucket名称
     * @param objectName  文件名称
     * @param stream      文件流
     * @param size        大小
     * @param contextType 类型
     * @throws Exception
     * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/PutObject">AWS
     * API Documentation</a>
     */
    public PutObjectResult putObject(String bucketName, String objectName, InputStream stream, long size,
                                     String contextType) throws Exception {
        // String fileName = getFileName(objectName);
        byte[] bytes = IOUtils.toByteArray(stream);
        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setContentLength(size);
        objectMetadata.setContentType(contextType);
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        // 上传
        return amazonS3.putObject(bucketName, objectName, byteArrayInputStream, objectMetadata);

    }

    /**
     * 获取文件信息
     *
     * @param bucketName bucket名称
     * @param objectName 文件名称
     * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/GetObject">AWS
     * API Documentation</a>
     */
    public S3Object getObjectInfo(String bucketName, String objectName) throws Exception {
        @Cleanup
        S3Object object = amazonS3.getObject(bucketName, objectName);
        return object;
    }

    /**
     * 删除文件
     *
     * @param bucketName bucket名称
     * @param objectName 文件名称
     * @throws Exception
     * @see <a href=
     * "http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/DeleteObject">AWS API
     * Documentation</a>
     */
    public void removeObject(String bucketName, String objectName) throws Exception {
        amazonS3.deleteObject(bucketName, objectName);
    }

    @Override
    public void afterPropertiesSet() {
        ClientConfiguration clientConfiguration = new ClientConfiguration();
        clientConfiguration.setMaxConnections(properties.getOss().getMaxConnections());

        AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder.EndpointConfiguration(
                properties.getOss().getEndpoint(), properties.getOss().getRegion());
        AWSCredentials awsCredentials = new BasicAWSCredentials(properties.getOss().getAccessKey(),
                properties.getOss().getSecretKey());
        AWSCredentialsProvider awsCredentialsProvider = new AWSStaticCredentialsProvider(awsCredentials);
        this.amazonS3 = AmazonS3Client.builder().withEndpointConfiguration(endpointConfiguration)
                .withClientConfiguration(clientConfiguration).withCredentials(awsCredentialsProvider)
                .disableChunkedEncoding().withPathStyleAccessEnabled(properties.getOss().getPathStyleAccess()).build();
    }

}
